#include "sudo_server.h"

#include "netlib/base/logger.h"
#include "netlib/net/socket/tcp_connection.h"

const int SudoServer::kLength = 81;

SudoServer::SudoServer(net::EventLoop* loop, const net::InetAddress& addr, int n_threads) :
    loop_(loop), server_(loop, addr, "SudoServer") {
	using namespace std::placeholders;
	server_.SetConnectionCallback(
	    [this](const net::TcpConnectionPtr& conn) { OnConnection(conn); });
	server_.SetMessageCallback([this](const net::TcpConnectionPtr& conn, net::Buffer* buf,
	                                  Timestamp time) { OnMessage(conn, buf, time); });
	server_.SetThreadNum(n_threads);
}

void SudoServer::Start() { server_.Start(); }

void SudoServer::OnConnection(const net::TcpConnectionPtr& conn) {
	LOG_INFO << "SudoServer <-> " << conn->PeerAddress().ToIpPort() << " : "
	         << conn->LocalAddress().ToIpPort() << " is " << (conn->Connected() ? "UP" : "DOWN");
}

void SudoServer::OnMessage(const net::TcpConnectionPtr& conn, net::Buffer* buf, Timestamp time) {
	size_t len = buf->ReadableBytes();
	LOG_INFO << conn->Name() << "receive " << len << "bytes data at " << time.ToFormattedString();
	while (len >= kLength + 2) {
		const char* crlf = buf->FindCrlf();
		if (crlf) {
			std::string request(buf->Peek(), crlf);
			std::string id;
			buf->RetrieveUntil(crlf + 2);
			std::string::iterator it = find(request.begin(), request.end(), ':');
			if (it != request.end()) {
				id.assign(request.begin(), it);
				request.erase(request.begin(), it + 1);
			}
			if (kLength == request.size()) {
				// std::string result = solveSudo(request);
				// if (id.empty()) {
				// 	conn->send(result + "\r\n");
				// } else {
				// 	conn->send(id + ":" + result + "\r\n");
				// }
				// thread_pool_.run(std::bind(solve, conn, request, id));
				Solve(conn, request, id);
			} else {
				conn->Send("Bad Request! Length is not right!\r\n");
				LOG_ERROR << "Length is not right!";
				conn->Shutdown();
			}
		} else {
			break;
		}
	}
}

std::string SudoServer::SolveSudo(const std::string& puzzle) {
	std::string result(puzzle);
	if (!Backtracking(result, 0)) {
		result.assign("NoSolution!");
	}
	return result;
}

bool SudoServer::Backtracking(std::string& result, int idx) {
	if (idx == kLength) {
		return true;
	}
	if ('0' != result[idx]) {
		return Backtracking(result, idx + 1);
	}

	for (int i = 1; i < 10; ++i) {
		if (IsOk(result, idx, i)) {
			result[idx] += static_cast<char>(i);
			if (Backtracking(result, idx + 1)) {
				return true;
			}
			result[idx] -= static_cast<char>(i);
		}
	}
	return false;
}

bool SudoServer::IsOk(const std::string& result, int idx, int val) {
	int row_idx = idx / 9;
	for (int i = row_idx * 9; i < row_idx * 9 + 9; ++i) {
		if (i == idx) {
			continue;
		}
		if (result[i] - '0' == val) {
			return false;
		}
	}
	int col_idx = idx % 9;
	for (int i = col_idx; i < kLength; i += 9) {
		if (i == idx) {
			continue;
		}
		if (result[i] - '0' == val) {
			return false;
		}
	}
	for (int i = row_idx / 3 * 3; i < row_idx / 3 * 3 + 3; ++i) {
		for (int j = col_idx / 3 * 3; j < col_idx / 3 * 3 + 3; ++j) {
			int real_idx = i * 9 + j;
			if (real_idx == idx) {
				continue;
			}
			if (result[real_idx] - '0' == val) {
				return false;
			}
		}
	}
	return true;
}

void SudoServer::Solve(const net::TcpConnectionPtr& conn,
                       const std::string& puzzle,
                       const std::string& id) {
	std::string result(SolveSudo(puzzle));
	if (id.empty()) {
		conn->Send(result + "\r\n");
	} else {
		conn->Send(id + ":" + result + "\r\n");
	}
}
